/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
  ArkFile,
  ArkMethod,
  ArkNamespace,
  Stmt,
  ts
} from 'arkanalyzer';

import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import {
  ClassMatcher,
  FieldMatcher,
  FileMatcher,
  MatcherCallback,
  MatcherTypes,
  MethodMatcher,
} from "../../matcher/Matchers";
import { Defects, IssueReport } from "../../model/Defects";
import { ALERT_LEVEL, Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { RuleFix } from '../../model/Fix';
import { FixUtils } from '../../utils/common/FixUtils';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'PreferOptionalChainCheck');
export type Options = [
  {
    /** Allow autofixers that will change the return type of the expression. This option is considered unsafe as it may break the build. */
    allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing?: boolean;
    /** Check operands that are typed as `any` when inspecting "loose boolean" operands. */
    checkAny?: boolean;
    /** Check operands that are typed as `bigint` when inspecting "loose boolean" operands. */
    checkBigInt?: boolean;
    /** Check operands that are typed as `boolean` when inspecting "loose boolean" operands. */
    checkBoolean?: boolean;
    /** Check operands that are typed as `number` when inspecting "loose boolean" operands. */
    checkNumber?: boolean;
    /** Check operands that are typed as `string` when inspecting "loose boolean" operands. */
    checkString?: boolean;
    /** Check operands that are typed as `unknown` when inspecting "loose boolean" operands. */
    checkUnknown?: boolean;
    /** Skip operands that are not typed with `null` and/or `undefined` when inspecting "loose boolean" operands. */
    requireNullish?: boolean;
  },
];
interface Diagnostic {
  startLine: number;
  startColumn: number;
  errorType: string;
  fixRange?: { start: number; end: number };
  fixMessage?: string;
  suffix?: string
}

export class PreferOptionalChainCheck implements BaseChecker {
  private defaultOptions: Options = [
    {
      checkAny: true,
      checkUnknown: true,
      checkString: true,
      checkNumber: true,
      checkBoolean: true,
      checkBigInt: true,
      requireNullish: false,
      allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing: false,
    },
  ];
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];

  public metaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: 'docs/prefer-optional-chain-check.md',
    description: "Prefer using an optional chain expression instead, as it's more concise and easier to read.",
  };

  private fileMatcher: FileMatcher = {
    matcherType: MatcherTypes.FILE,
  };

  public registerMatchers(): MatcherCallback[] {
    const matchFileCb: MatcherCallback = {
      matcher: this.fileMatcher,
      callback: this.check
    }
    return [matchFileCb];
  }



  public check = (targetField: ArkFile) => {
    this.defaultOptions = this.getOption();
    const severity = this.rule.alert ?? this.metaData.severity;
    const filePath = targetField.getFilePath();
    const classes = targetField.getClasses();
      let record : Map<string,string> = new Map();
      classes.forEach(clazz => {
        const methods = clazz.getMethods();
        methods.forEach(method => {
          const stmts = method?.getBody()?.getCfg().getStmts() ?? [];
          stmts.forEach(stmt => {
            const originText = stmt.getOriginalText() ?? '';
            const position = stmt.getOriginPositionInfo();
            const key = `${position.getLineNo()}:${position.getColNo()}`;
            if(!record.has(key) && originText && (originText.includes("&&") || originText.includes("||"))) {
              this.executeCheck(originText, filePath, severity, stmt, targetField);
              record.set(key,originText);
            }
          })
        })
      })
    
  }

  private executeCheck(code: string, filePath: string, severity: ALERT_LEVEL, stmt: Stmt, arkFile: ArkFile) {
    const myInvalidPositions = this.checkOptionalChain(code);
    this.filterResult(myInvalidPositions, filePath, severity,stmt, arkFile);
  }

  private filterResult(myInvalidPositions: Diagnostic[], filePath: string, severity: ALERT_LEVEL, stmt: Stmt, arkFile: ArkFile) {
    let map: Map<String, Diagnostic> = new Map();
    myInvalidPositions.forEach((pos) => {
      const key = `${pos.startLine}:${pos.startColumn}`;
      if (map.has(key)) {
        const fixMessage = map.get(key)?.fixMessage ?? '';
        const currFixMessage = pos.fixMessage ?? '';
        if (currFixMessage.length > fixMessage.length) {
          map.set(key, pos);
        }
      } else {
        map.set(key, pos);
      }
    });
    map.forEach((pos) => {
      pos.fixMessage += pos.suffix ?? "";
      if (pos.errorType === 'always') {
        pos.fixRange = undefined;
        this.addIssueReport(filePath, pos, severity,stmt, arkFile);
      } else {
        const config = this.defaultOptions[0];
        if (!config.requireNullish) {
          if (config.checkAny && pos.errorType === 'any') {
            this.addIssueReport(filePath, pos, severity,stmt, arkFile);
          } else if (config.checkString) {
            if (pos.errorType === 'string') {
              if (!config.allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing) {
                pos.fixRange = undefined;
              }
              this.addIssueReport(filePath, pos, severity,stmt, arkFile);
            }
            if (config.checkUnknown && pos.errorType === 'unknown') {
              this.addIssueReport(filePath, pos, severity,stmt, arkFile);
            } else if (config.checkBigInt && pos.errorType === 'bigint') {
              if (!config.allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing) {
                pos.fixRange = undefined;
              }
              this.addIssueReport(filePath, pos, severity,stmt, arkFile);
            } else if (config.checkNumber && pos.errorType === 'number') {
              if (!config.allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing) {
                pos.fixRange = undefined;
              }
              this.addIssueReport(filePath, pos, severity,stmt, arkFile);
            }
          }
        }
      }

    });
  }

  private addIssueReport(filePath: string, pos: Diagnostic, severity: number, stmt: Stmt, arkFile: ArkFile) {
    const startLine = pos.startLine + stmt.getOriginPositionInfo().getLineNo() - 1;
    const startCol = pos.startColumn + (pos.startLine === 1 ? (stmt.getOriginPositionInfo().getColNo() - 1) : 0);
    const description = "Prefer using an optional chain expression instead, as it's more concise and easier to read.";
    const defect = new Defects(startLine, startCol, startCol, description, severity, this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
    const ruleFix = this.createFix(pos,stmt,arkFile);
    this.issues.push(new IssueReport(defect, ruleFix));
    RuleListUtil.push(defect);
  }

  private createFix(pos: Diagnostic, stmt: Stmt, arkFile: ArkFile): RuleFix | undefined {
    if (pos.fixRange) {
      const startVerify = FixUtils.getRangeStart(arkFile,stmt);
      return { range: [pos.fixRange.start + startVerify , pos.fixRange.end + startVerify], text: pos.fixMessage ?? '' };
    }
    return undefined;

  }

  private getOption(): Options {
    let option: Options;
    if (this.rule && this.rule.option) {
      option = this.rule.option as Options;
      if (option[0]) {
        if (option[0]?.allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing === undefined) {
          option[0].allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing = false;
        }
        if (option[0]?.checkAny === undefined) {
          option[0].checkAny = true;
        }
        if (option[0]?.checkUnknown === undefined) {
          option[0].checkUnknown = true;
        }
        if (option[0]?.checkString === undefined) {
          option[0].checkString = true;
        }
        if (option[0]?.checkNumber === undefined) {
          option[0].checkNumber = true;
        }
        if (option[0]?.checkBoolean === undefined) {
          option[0].checkBoolean = true;
        }
        if (option[0]?.checkBigInt === undefined) {
          option[0].checkBigInt = true;
        }
        if (option[0]?.requireNullish === undefined) {
          option[0].requireNullish = false;
        }
        return option;
      }
    }
    return this.defaultOptions;
  }

  private checkOptionalChain(code: string): Diagnostic[] {
    const sourceFile = ts.createSourceFile('file.ts', code, ts.ScriptTarget.Latest, true);
    const diagnostics: Diagnostic[] = [];

    // 收集所有变量声明的类型信息
    const variableTypes = new Map<string, string>();
    ts.forEachChild(sourceFile, node => {
      if (ts.isVariableStatement(node)) {
        node.declarationList.declarations.forEach(decl => {
          if (ts.isIdentifier(decl.name)) {
            const varName = decl.name.text;
            let type = 'any';
            if (decl.type) {
              type = getTypeText(decl.type, sourceFile);
            }
            variableTypes.set(varName, type);
          }
        });
      }
    });

    function getTypeText(typeNode: ts.TypeNode, sourceFile: ts.SourceFile): string {
      if (ts.isUnionTypeNode(typeNode)) {
        return typeNode.types[0].getText(sourceFile);
      }
      return typeNode.getText(sourceFile);
    }

    function getErrorType(identifier: ts.Identifier): string {
      return variableTypes.get(identifier.text) || 'any';
    }

    function addDiagnostic(
      node: ts.Node,
      errorType: string,
      fixMessage?: string,
      fixRange?: [number, number],
      suffix?: string,
    ) {
      const start = node.getStart(sourceFile);
      const { line, character } = sourceFile.getLineAndCharacterOfPosition(start);
      const diagnostic: Diagnostic = {
        startLine: line + 1,
        startColumn: character + 1,
        errorType,
      };
      if (fixMessage && fixRange) {
        diagnostic.fixRange = { start: fixRange[0], end: fixRange[1] };
        diagnostic.fixMessage = fixMessage;
        diagnostic.suffix = suffix;
      }
      diagnostics.push(diagnostic);
    }


    function checkLogicalExpression(node: ts.BinaryExpression) {
      if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
        checkLogicalAnd(node);
      } else if (node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
        checkLogicalOr(node);
        checkLogicalOr1(node);
      }
    }

    function checkLogicalAnd(node: ts.BinaryExpression) {
      const chain = flattenLogicalAnd(node);
      if (chain.length < 2) return;

      // 检查是否连续的属性访问链
      let isValid = true;
      for (let i = 1; i < chain.length; i++) {
        const prev = chain[i - 1];
        const current = chain[i];
        if (!isPropertyAccessChain(prev, current)) {
          isValid = false;
          break;
        }
      }

      if (isValid) {
        const fixMessage = buildOptionalChain(chain);
        if (fixMessage) {
          const last = chain[chain.length -1];
          let suffix = '';
          if (ts.isBinaryExpression(last)) {
            const left = buildOptionalChain([last.left]);
            if (left) {
              const right = `${last.operatorToken.getText(sourceFile)} ${last.right.getText(sourceFile)}`;
              if (right === "!== null" || right === "!= null" || right === "!== undefined" || right === "!= undefined") {
                suffix = ` ${last.operatorToken.getText(sourceFile)} ${last.right.getText(sourceFile)}`;
              } 
            }
          }

          const start = node.getStart(sourceFile);
          const end = node.getEnd();
          addDiagnostic(node, 'any', fixMessage, [start, end],suffix);
        }
        return;
      }

      // 检查每个单独的 && 表达式
      let current: ts.Node = node;
      while (ts.isBinaryExpression(current) && current.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
        const left = current.left;
        const right = current.right;
        if (isReplaceableRight(left, right)) {
          const fixMessage = buildRightReplacement(left, right);
          if (fixMessage) {
            const start = current.getStart(sourceFile);
            const end = current.getEnd();
            addDiagnostic(current, getErrorTypeForExpression(left), fixMessage, [start, end]);
          }
        }
        current = current.left;
      }
    }

    function buildRightReplacement1(left: ts.Expression, right: ts.Expression): string | undefined {
      // 解包逻辑非表达式
      const unwrappedLeft = unwrapLogicalNot(left);
      const unwrappedRight = unwrapLogicalNot(right);

      // 生成可选链核心部分
      let coreReplacement: string | undefined;
      if (ts.isPropertyAccessExpression(unwrappedRight)) {
        coreReplacement = `${unwrappedLeft?.getText(sourceFile)}?.${unwrappedRight.name.text}`;
      } else if (ts.isElementAccessExpression(unwrappedRight)) {
        const index = unwrappedRight.argumentExpression.getText(sourceFile);
        coreReplacement = `${unwrappedLeft?.getText(sourceFile)}?.[${index}]`;
      } else if (ts.isCallExpression(unwrappedRight)) {
        coreReplacement = `${unwrappedLeft?.getText(sourceFile)}?.()`;
      }

      // 保留外层的逻辑非
      if (coreReplacement) {
        const hasLeftNot = ts.isPrefixUnaryExpression(left);
        const hasRightNot = ts.isPrefixUnaryExpression(right);
        return (hasLeftNot || hasRightNot) ? `!${coreReplacement}` : coreReplacement;
      }
      return undefined;
    }

    function isReplaceableRight1(left: ts.Expression, right: ts.Expression): boolean {
      // 解包逻辑非表达式
      const unwrappedLeft = unwrapLogicalNot(left);
      const unwrappedRight = unwrapLogicalNot(right);

      // 核心逻辑：检查右侧表达式是否基于左侧表达式的属性链
      if (
        ts.isPropertyAccessExpression(unwrappedRight) &&
        areEquivalent(unwrappedRight.expression, unwrappedLeft)
      ) return true;

      if (
        ts.isElementAccessExpression(unwrappedRight) &&
        areEquivalent(unwrappedRight.expression, unwrappedLeft)
      ) return true;

      if (
        ts.isCallExpression(unwrappedRight) &&
        areEquivalent(unwrappedRight.expression, unwrappedLeft)
      ) return true;

      return false;
    }

    function checkLogicalOr1(node: ts.BinaryExpression) {
      let current: ts.Node = node;
      while (ts.isBinaryExpression(current) && current.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
        const left = current.left;
        const right = current.right;

        // 关键修改：直接检查左右表达式的关系，不依赖 flatten 链
        if (isReplaceableRight1(left, right)) {
          const fixMessage = buildRightReplacement1(left, right);
          if (fixMessage) {
            const start = current.getStart(sourceFile);
            const end = current.getEnd();
            addDiagnostic(
              current,
              getErrorTypeForExpression(unwrapLogicalNot(left)),
              fixMessage,
              [start, end]
            );
          }
        }
        current = current.left; // 继续向左侧递归检测
      }
    }

    function checkLogicalOr(node: ts.BinaryExpression) {
      let current: ts.Node = node;
      while (ts.isBinaryExpression(current) && current.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
        const left = current.left;
        const right = current.right;

        // 关键修改：直接检查左右表达式的关系，不依赖 flatten 链
        if (isReplaceableRight(left, right)) {
          const fixMessage = buildRightReplacement(left, right);
          if (fixMessage) {
            const start = current.getStart(sourceFile);
            const end = current.getEnd();
            addDiagnostic(
              current,
              getErrorTypeForExpression(unwrapLogicalNot(left)),
              fixMessage,
              [start, end]
            );
          }
        }
        current = current.left; // 继续向左侧递归检测
      }
    }

    function isLogicalNotChain(prev: ts.Expression, current: ts.Expression): boolean {
      if (ts.isPrefixUnaryExpression(prev) && prev.operator === ts.SyntaxKind.ExclamationToken) {
        if (ts.isPrefixUnaryExpression(current) && current.operator === ts.SyntaxKind.ExclamationToken) {
          return areEquivalent(prev.operand, current.operand);
        }
      }
      return false;
    }

    function flattenLogicalOr(node: ts.Expression): ts.Expression[] {
      const chain: ts.Expression[] = [];
      while (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
        chain.unshift(node.right);
        node = node.left;
      }
      chain.unshift(node);
      return chain;
    }

    function getErrorTypeForExpression(expr: ts.Expression): string {
      if (ts.isIdentifier(expr)) {
        return getErrorType(expr);
      } else if (ts.isPropertyAccessExpression(expr)) {
        return getErrorTypeForExpression(expr.expression);
      }
      return 'any';
    }

    function buildRightReplacement(left: ts.Expression, right: ts.Expression): string | undefined {
      if (ts.isPropertyAccessExpression(right)) {
        return `${left.getText(sourceFile)}?.${right.name.text}`;
      } else if (ts.isElementAccessExpression(right)) {
        const index = right.argumentExpression.getText(sourceFile);
        return `${left.getText(sourceFile)}?.[${index}]`;
      } else if (ts.isCallExpression(right)) {
        const expr = right.expression;
        if (ts.isPropertyAccessExpression(expr)) {
          return `${left.getText(sourceFile)}?.${expr.name.text}()`;
        } else if (ts.isElementAccessExpression(expr)) {
          const index = expr.argumentExpression.getText(sourceFile);
          return `${left.getText(sourceFile)}?.[${index}]()`;
        } else if (areEquivalent(expr, left)) {
          return `${left.getText(sourceFile)}?.()`;
        }
      } else if (ts.isBinaryExpression(right)) {
        const leftReplacement = buildRightReplacement(left, right.left);
        if (leftReplacement) {
          return `${leftReplacement} ${right.operatorToken.getText(sourceFile)} ${right.right.getText(sourceFile)}`;
        }
      }
      return undefined;
    }

    function isReplaceableRight(left: ts.Expression, right: ts.Expression): boolean {
      // 处理属性访问：a && a.b
      if (ts.isPropertyAccessExpression(right) && areEquivalent(right.expression, left)) return true;

      // 处理元素访问：a && a['b']
      if (ts.isElementAccessExpression(right) && areEquivalent(right.expression, left)) return true;

      // 处理方法调用：a && a.method()
      if (ts.isCallExpression(right)) {
        const expr = right.expression;
        // 检查调用表达式的主体是否与 left 一致（如 a.method() 中的 a.method）
        if (
          (ts.isPropertyAccessExpression(expr) ||
            ts.isElementAccessExpression(expr)
          ) && areEquivalent(expr.expression, left)) {
          return true;
        }
        // 直接调用：a && a()
        if (areEquivalent(expr, left)) return true;
      }

      // 处理空值检查：a && a.b != null
      if (ts.isBinaryExpression(right) && isNullCheck(right)) {
        const leftExpr = right.left;
        if (
          (ts.isPropertyAccessExpression(leftExpr) ||
            ts.isElementAccessExpression(leftExpr) ||
            ts.isCallExpression(leftExpr)
          ) && areEquivalent(leftExpr.expression, left)) {
          return true;
        }
      }

      return false;
    }

    function isNullCheck(node: ts.BinaryExpression): boolean {
      const nullUndefined = [ts.SyntaxKind.NullKeyword, ts.SyntaxKind.UndefinedKeyword];
      return (node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken ||
        node.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken ||
        node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken ||
        node.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) &&
        (nullUndefined.includes(node.right.kind) || nullUndefined.includes(node.left.kind));
    }

    function flattenLogicalAnd(node: ts.Expression): ts.Expression[] {
      const chain: ts.Expression[] = [];
      while (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
        chain.unshift(node.right);
        node = node.left;
      }
      chain.unshift(node);
      return chain;
    }

    function isPropertyAccessChain(prev: ts.Expression, current: ts.Expression): boolean {
      if (ts.isPropertyAccessExpression(current) && areEquivalent(current.expression, prev)) return true;
      if (ts.isElementAccessExpression(current) && areEquivalent(current.expression, prev)) return true;
      if (ts.isCallExpression(current) && areEquivalent(current.expression, prev)) return true;
      const code = current.getText(sourceFile).trim();
      if (ts.isBinaryExpression(current)) {
        // 检查 right.left 是否是 PropertyAccessExpression、ElementAccessExpression 或 CallExpression
        const right = `${current.operatorToken.getText(sourceFile)} ${current.right.getText(sourceFile)}`;
        if(isNullCheck(current) || code.endsWith("!= undefined") || code.endsWith("!== undefined") || code.endsWith("!= null") || code.endsWith("!== null")){
          if (ts.isPropertyAccessExpression(current.left) && areEquivalent(current.left.expression, prev)) return true;
          if (ts.isElementAccessExpression(current.left) && areEquivalent(current.left.expression, prev)) return true;
          if (ts.isCallExpression(current.left) && areEquivalent(current.left.expression, prev)) return true;
        }
      }
      return false;
    }

    function buildOptionalChain(chain: ts.Expression[]): string | undefined {
      let result = chain[0].getText(sourceFile);
      for (let i = 1; i < chain.length; i++) {
        const expr = chain[i];
        const preExpr = chain[i -1];
        if (ts.isPropertyAccessExpression(expr)) {
          const curr = expr.getText(sourceFile);
          let pre = chain[i - 1].getText(sourceFile);
          if(ts.isBinaryExpression(preExpr)) {
            pre = buildOptionalChain([preExpr.left]) ?? "";
          }
          const nail = curr.substring(curr.indexOf(pre) + pre.length);
          if (nail) {
            result += `${nail.startsWith("?") ? "" : "?"}${nail.startsWith("[") ? "." : ""}${nail}`;
          }
        } else if (ts.isElementAccessExpression(expr)) {
          const index = expr.argumentExpression.getText(sourceFile);
          result += `?.[${index}]`;
        } else if (ts.isCallExpression(expr)) {
          const curr = expr.getText(sourceFile);
          const pre = chain[i - 1].getText(sourceFile);
          const nail = curr.substring(curr.indexOf(pre) + pre.length);
          result += `${nail === "()" ? "?.()" : `${nail.startsWith("?") ? "" : "?"}${nail.startsWith("[") ? "." : ""}${nail}`}`;
        } else if (ts.isBinaryExpression(expr)) {
          const left = buildOptionalChain([expr.left]);
          if (left) {
            const right = `${expr.operatorToken.getText(sourceFile)} ${expr.right.getText(sourceFile)}`;
            if (right === "!= null" || right === "!== null" || right === "!= undefined" || right === "!== undefined") {
              const curr = left;
              let pre = chain[i - 1].getText(sourceFile);
              if(ts.isBinaryExpression(preExpr)) {
                pre = buildOptionalChain([preExpr.left]) ?? "";
              }
              const nail = curr.substring(curr.indexOf(pre) + pre.length);
              if (nail) {
                result += `${nail.startsWith("?") ? "" : "?"}${nail.startsWith("[") ? "." : ""}${nail}`;
              }
              
            } else {
              result = `${left} ${expr.operatorToken.getText(sourceFile)} ${expr.right.getText(sourceFile)}`;
            }
          } else {
            return undefined;
          }
        } else {
          return undefined;
        }
      }
      return result;
    }

    function areEquivalent(a: ts.Expression, b: ts.Expression): boolean {
      const aText = a.getText(sourceFile);
      let bText = b.getText(sourceFile);
      const aParentText = a.parent.getText(sourceFile);
      if (bText.trim().endsWith("!= null")) {
        bText = bText.trim().substring(0, bText.trim().length - 7).trim()
      } else if (bText.trim().endsWith("!=null")) {
        bText = bText.trim().substring(0, bText.trim().length - 6).trim()
      } else if (bText.trim().endsWith("!== undefined")) {
        bText = bText.trim().substring(0, bText.trim().length - 13).trim()
      } else if (bText.trim().endsWith("!==undefined")) {
        bText = bText.trim().substring(0, bText.trim().length - 12).trim()
      }
      return aText.includes(bText) || aParentText === bText;
    }

    function unwrapLogicalNot(node: ts.Expression): ts.Expression {
      return ts.isPrefixUnaryExpression(node) &&
        node.operator === ts.SyntaxKind.ExclamationToken
        ? node.operand
        : node;
    }


    // 新增：检测 (expr || {}).property 模式
    function checkLogicalOrWithDefault(node: ts.Expression): ts.Expression | null {
      if (
        ts.isParenthesizedExpression(node) &&
        ts.isBinaryExpression(node.expression) &&
        node.expression.operatorToken.kind === ts.SyntaxKind.BarBarToken
      ) {
        const left = node.expression.left;
        const right = node.expression.right;
        if (
          ts.isObjectLiteralExpression(right) &&
          right.properties.length === 0
        ) {
          return left;
        }
      }
      return null;
    }

    // 新增：递归检查嵌套结构
    function checkNestedLogicalOr(node: ts.Node) {
      // 处理属性访问和元素访问
      if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
        const expression = node.expression;
        const convertedExpr = checkLogicalOrWithDefault(expression);
        if (convertedExpr) {
          // 生成诊断信息（不提供修复建议）
          addDiagnostic(
            node,
            'always',
            undefined,  // fixMessage 为 undefined
            undefined   // fixRange 为 undefined
          );
        }
      }

      // 递归检查所有子节点
      ts.forEachChild(node, checkNestedLogicalOr);
    }

    // 遍历AST
    ts.forEachChild(sourceFile, function recur(node) {
      checkNestedLogicalOr(node); // 新增嵌套检查
      if (ts.isBinaryExpression(node)) {
        checkLogicalExpression(node);
      }
      ts.forEachChild(node, recur);
    });

    return diagnostics;
  }


}